react refresher

2024-01-30

The following are notes from a refresher on React. The examples are mostly snippets of full components, so there's def stuff missing. However, this was meant as a way to write out what stood out post completion to help solidify things 'refreshed' or newly learned.

behind the curtain, createElement, old-school

Here, in creatElement the first arg is the element (in this case, div), the second would be an attributes (id/style/etc), and the third would be children (here it is an h1 tag, with no attributes and text). Note: React.createElement creates one instance of w.e component.

  1. example (basic):
const App = () => {
  return React.createElement(
    "div",
    {},
    React.createElement("h1", {}, "Adopt Me!")
  );
};

const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(React.createElement(App));
  1. example (w/props)
const Pet = (props) => {
  return React.createElement("div", {}, [
    React.createElement("h1", {}, props.name),
    React.createElement("h2", {}, props.animal),
    React.createElement("h2", {}, props.breed),
  ]);
};

const App = () => {
  return React.createElement("div", {}, [
    React.createElement("h1", {}, "Adopt Me!"),
    React.createElement(Pet, {
      name: "Luna",
      animal: "Dog",
      breed: "Havanese",
    }),
    React.createElement(Pet, {
      name: "Pepper",
      animal: "Bird",
      breed: "Cockatiel",
    }),
    React.createElement(Pet, { name: "Doink", animal: "Cat", breed: "Mix" }),
  ]);
};

const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(React.createElement(App));

This isnt something I'd expect to see much of as-is, but cool to know JSX is basically translating it all. So things start looking more like this (which is what I think we are all used to seeing these days).

const Pet = (props) => {
  return (
    <div>
      <h1>{props.name}</h1>
      <h2>{props.animal}</h2>
      <h2>{props.breed}</h2>
    </div>
  );
};

export default Pet;

tools

When you do something like create-react-app it's like a little starter kit wrapped with a bow :D. With that said, here are some things you probably don't have to think too much about when you hop onto something that already has stuff.

  • npm

    • a package panager for Node... for like ever I thought it stood for NodeJS Package Manager, apparently it does not. It also has packages for frontend. All types of useful stuff people have written on there that YOU get to use with every npm install somePackageHere you do. The latest and greatest versions from the npm registry.
  • prettier

    • code quality is defintely important when writing any type of code, prettier does exaxtly what it sounds like, it makes your code prettier. By this I mean, it can take care of formatting, things like spaces, break lines... w.e the predefined style is. This keeps your code consistent not on solo projects, but those you share with others.
  • esLint

    • while prettier can take care of things like formatting, a linter (like esLint) can enforce code styles that have to do with usage, such as syntax/patterns/a11y/etc. There def can be some overlap with these two tools. With that said, inside your .eslintrc.json prettier should go last in the extends[] that way it turns off whatever may be enabled by what comes before. note: eslint:recommended is a good starting point.
  • git

    • git is what you use to init your project, add/commit changes to push up to github. Basically, it keeps track of your project state.
    • .gitignore file to make sure you are not pushing up anything unnecessarily, ie: node_modules, .env, etc.
  • vite (vEEt)

    • a build tool of bundle goodness. This one was a new one for me, for those that have used Webpack, they fall under the same category. Another one, is Parcel. Presently, vite is the tool-of-choice in the React community.

hooks

const [name, setName] = useState(''); All hooks, even custom (made by you hooks) start with 'use' as a way to say 'hey this is a hook'. Also, the structure of the code above is basically destructing, useState returns an array, so here we are saying 'hey, that first thing put it as name and that second thing is gonna be setName'.

useState

State is called in order, this consistency is how React can keep track of where everything is and what has changed in comparison for a re-rendering. With that said, don't try and add them into like a conditional or something because that could mess with the order.

useEffect

Useful for when trying to synchronize with something external, ie: fetching api, etc. It's like 'hey I want this page rendered and then I want this other thing to happen' the effect .

Now, what's up with that empty array dependency warning from eslint? The dependencies array, when empty is something along the lines of (keeping with the fetch example) you want to make a request after initial load of page, but do not want it getting triggered on every change of name (from the useState exmaple up top). If certain of this wanted behavior, you can silence eslint with // eslint-disable-line react-hooks/exhaustive-deps. Make sure there's atleast an empty an array in there for this situation, without one it will assume it should run every time the hook changes.

Adding a function inside a useEffect, being defined in there, it isnt recreated on every render cycle.

controlled vs uncontrolled forms

"Controlled" would mean you have state controlling areas of the form. "Uncontrolled", you would not be setting a value, instead one would listen for submit events that would gather information within the form. Seems a good rule of thumb would be to go for uncontrolled unless you need some sort of dynamic validation (ie: location awareness, autocomplete, etc).

When working with controlled forms, the value attribute should always be present to force value to match state variable whenever it updates (onChange)

Sometimes, reacting to data change isn't what you are after but instead you just want a response to a click. In the case of forms, that could be a submit.

The clicking of the submit button calls the request:

<form
    onSubmit={(e) => {
        e.preventDefault();
        fetchWhateverYouAreFetching()
    }}
>
...

The e.preventDefault(); for the forgetful, it prevents or cancels the action like with a 'submit' button.

Note: the 'e' for event in the example above (in react) is a react synthetic event, which is basically the same as a normal DOM event but in cases of using TypeScriptcan be helpful to have that distinction in mind.

FormData // TODO

"The FormData interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the fetch()..." ref: mdn - FormData

{...whateverProps}

Unless the component is just a pass thru where it truly doesn't matter, best practice is to be explicit of what is being passed/expected. Also, for readablity sake, imo.

<Pet
    key={pet.id}
    {...pet}
/>

vs.

<Pet
    key={pet.id}
    name={pet.name}
    animal={pet.animal}
    breed={pet.breed}
    images={pet.images}
    location={`${pet.city}, ${pet.state}`}
/>

react dev tools

Dev bundle for React is quite large would suck to have that in production, you don't wanna be all sloooow and stuff out there in the real world... do you?

Using Webpack you would need to be more aware of this, however using Vite (vEEt) or something like Parcel it will auto change the environment variable when you `run build``.

NODE_ENV=development <=> NODE_ENV=production

react strict mode

<StrictMode><StrictMode/> wrapping your app with this may be a good idea to get additional warnings on using legacy features or things soon to be deprecated. Something odd about this, it runs functions on initialization 2x... why? NOTE TO SELF, GO FIND OUT WHY

routing

Wrap app with . Inside here whatever pages you want would go inside with each individual page using .

example:

<BrowserRouter>
    <header>
    <Link to="/">Find-a-Nom</Link>
    </header>
    <Routes>
    <Route path="/details/:id" element={<Details />} />
    <Route path="/" element={<SearchParams />} />
    </Routes>
</BrowserRouter>

Using <a> tags cause a rerender of the app due to browser navigating to a new page, use <Link to={/details/${id}} to prevent that. <Link> intercepets and handles cline-side, faster, better UX.

useParams allows you to capture params from React Router (with earlier example in mind, the :id). Note: this is possible due to available context from <BrowserRouter>

react-query

"it's a built in caching layer for these async data stores", typically you'd want to fetch something and not want to refetch if a user has already visited the page with that same info. So, react-query to the rescue, using React context to pass App's cache.

Wrap app (within BrowserRouter though) with

Note: react query cache is stored in memory. Ref: react query

HOC

Higher Order Compoenents tend to be referred to things such as or , they are not in themselves displaying anything but wrappers, providing context to the componenents within that wrap.

Class Components

Class components are the old school way of doing things in React, although still out in the wild and sometimes may come into play (ie: Error Boundary), the community has migrated more towards using function components.

arrow functions vs normal functions

When writing a handleFn for an onClick within a class component, using an arrow fn helps with scope ensuring the this is the this we are expecting (scope of where fn was defined). In this particular case, it would be the component we are in vs an undefined/etc.

using data-*

Allows you to store information that isnt exactly needed in the HTML but used for some interaction case within Javascript.

Example:

<img
    key={image}
    src={image}
    alt="animal thumbnail"
    **data-index={index}**
/>

"Overall, data-* attributes offer a flexible and convenient way to extend the capabilities of HTML elements, enabling richer interactions and better organization of data within a web page."

changing str => integer

+event.target.dataset.index, DOM stuff comes back as strings.

ErrorBoundary

Error boundaries can only catch errors of child components, so although one may be inclined to wrap from within the return of the component you want "wrapped" it will not work because you are essntially inside the component.

A work-around would be to create a funtion that would then return the ErrorBoundary wrapping the wanted component and then exporting that.

Example:

function DetailsErrorBoundary(props) {
  const errMsg = (
    <p>
      There was an error with this listing. <Link to="/">Click here</Link> to
      return to homepage.
    </p>
  );
  return (
    <ErrorBoundary componentError={errMsg}>
      <Details {...props} />
    </ErrorBoundary>
  );
}

export default DetailsErrorBoundary;

In this example Details doesn not actually take any props, however, if it would this would be necessary so they would pass through intended behavior as expected.

In using {...props}, DetailsErrorBoundary isnt meant to have an opinion on the props going in, Detail should care nothing about props as it is meant to pass through seamlessly.

The other thing to note here is the prop/attrubute componentError, this way the ErrorBoundary is reusuable and can take in w.e message specific to component.

And where does this prop/attr componentError come from? Fom withing the ErrorBoundary class component:

  render() {
    if (this.state.hasError) {
      return this.props.componentError
    }
    return this.props.children;
  }

static methods

Static methods, an es6 thing, where you call the method straight from the class in comparison to having to instantiate first.

ErrorBoundary.getDerivedStateFromError()

vs.

eb = new ErrorBoundary()
eb.getDerivedErrorFromState()

modals + portals

Using portals give you the ability to insert a component, in this case, a modal into another area outside the root element in the DOM.

Example (from within an .html):

    ...
    <div id="modal"></div>
    <div id="root">not rendered</div>
    ...

Intrestingly enough I always hear how horrible using modals is and that everyone and their mother should stop using it, but people love them... not going to lie... I sometimes like them too, seeing them (maybe it's cuz something pops up, like OH! Hello there. I don't know :D)

Example (MOdal component):

import { useEffect, useRef } from "react";
import { createPortal } from "react-dom";

const Modal = ({ children }) => {
  const elRef = useRef(null);
  if (!elRef.current) {
    elRef.current = document.createElement("div");
  }

  useEffect(() => {
    const modalRoot = document.getElementById("modal");
    modalRoot.appendChild(elRef.current);

    // 👇 basically a componentWillUnmount, but for a fn component
    return () => modalRoot.removeChild(elRef.current);
  }, []);

  return createPortal(<div>{children}</div>, elRef.current);
};

export default Modal;

To add to the example above, particularly the return in the useEffect:

  • it is basically a componentWillUnmount (used in a class component), but for a fn component
  • returning anything within an effect will run right before it totally unmounts
  • important to do in cases as these because one wouldnt want infinite divs left hanging out

useRef

In the above case, we want to come back to using the ssame div. Kinda like (for those that like cookies), you have a jar that you retrieve to get cookies and when you get more cookies, that same jar is where you want to put them. At least that's how it kinda made sense in my head (eventhough I do not like cookies or have a cookie jar).

  • apparently they can be compared to instance variables (this.someVarHere for classes), but for fn components.
  • for referring to the same DOM element...
  • cointainers of state living outside a functions closure, so each time you refer to elRef.current it is in fact the same element.
  • differs from a variable using useState, what is returned depends on the state at the time when the function was called, which is something we would want with async calls/effects
  • rendering to a different part of the DOM, yet still referencing state within a component within the root element (portalsss magicsss)
  • although different DOM trees, it's all coming from the same DOM tree in React, in which case event bubbling can be used.

Example (modal being used):

    ...
    ...
    const [showModal, setShowModal] = useState(false);
    ...
    ...
    return (
        ...
        <button onClick={() => setShowModal(true)}>Adopt {pet.name}</button>
        {showModal ? (
          <Modal>
            <div>
              <h1>Would you like to adopt {pet.name}?</h1>
              <div className="buttons">
                <button>Yes</button>
                <button onClick={() => setShowModal(false)}>No</button>
              </div>
            </div>
          </Modal>
        ) : null}
    )

Context

State, not confined to component being called from, but global. This seems useful for smaller amounts of state, however if racking up the amount of context may be better off with something like Redux. From what I gather, there isn't much reason to use both.

When to maybe use something like this? Perhaps, a shopping cart or some user detail for someone logged in, a theme of some sort. Basically, any time you truly need access to some sort of info from various parts of that app.

So, how to do this? react doc - create context

  1. example (createContext):
import { createContext } from "react";

const AdoptedPetContext = createContext(null);

export default AdoptedPetContext;

  1. example (wrapping App with context made):
const adoptedPet = useState(null);
return (
    <AdoptedPetContext.Provider value={adoptedPet}>
        ...
        ...
        ...
    </AdoptedPetContext.Provider>
)

Here, instead of the usual way you see the state hook, destructured, we can pass it fully to use elsewhere in the app as needed.

  1. example (using context):
const [, setAdoptedPet] = useContext(AdoptedPetContext)
...
...
...
return (
    ...
    ...
    ...
    <button
        onClick={() => {
            setAdoptedPet(pet);
        }}
    >
    ...
    ...
)
 

Here, we are taking out the setter from the state we set up in the previous example. The first part of the destructuring, where we would expect something like adoptedPet is not necessary here and therefore can stated in this way (or using _). Now when we click this button, it sets pet object of information for us to grab in another part of the app.

  1. example (using the state previously set 👆):
const [adoptedPet] = useContext(AdoptedPetContext)
...
...
...
return (
    ...
    ...
    {adoptedPet ? (
        <div className="pet image-container">
            <img src={adoptedPet.images[0]} alt={adoptedPet.name} />
        </div>
    ) : null}
    ...
    ...
    ...
)

Here, one can grab the state that was set previously pet. It checks for it and only if present will it show this div that has properties off adoptedPet (pet object set prior within setAdoptedPet). In this case, an image that uses the first image and name.

  1. lastly, there was a navigate('/') (navigates programmatically) ommitted in the button where we set state in example 3. Example below.
const navigate = useNavigate();
const [, setAdoptedPet] = useContext(AdoptedPetContext)
...
...
...
return (
    ...
    ...
    ...
    <button
        onClick={() => {
            setAdoptedPet(pet);
            navigate("/");
        }}
    >
    ...
    ...
)
 

Ref: React Router Docs - useNavigate

logging

Tools for error monitoring.


← go back